/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.caching.stats;

import io.iec.edp.caf.caching.api.Cache;
import io.iec.edp.caf.caching.cache.LayeringCache;
import io.iec.edp.caf.caching.manager.AbstractCacheManager;
import io.iec.edp.caf.caching.api.CacheManager;
import io.iec.edp.caf.caching.setting.LayeringCacheSetting;
import io.iec.edp.caf.caching.support.Lock;
import io.iec.edp.caf.caching.util.RedisHelper;
import io.iec.edp.caf.commons.core.SerializerFactory;
import io.iec.edp.caf.commons.core.api.DataSerializer;
import io.iec.edp.caf.commons.core.enums.SerializeType;
import io.iec.edp.caf.commons.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 统计服务
 */
public class StatsService {
    private static Logger logger = LoggerFactory.getLogger(StatsService.class);

    /**
     * 缓存统计数据前缀
     */
    public static final String CACHE_STATS_KEY_PREFIX = "layering-cache:cache_stats_info:";

    /**
     * 定时任务线程池
     */
    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50);

    /**
     * {@link AbstractCacheManager }
     */
    private AbstractCacheManager cacheManager;

    /**
     * 获取缓存统计list
     *
     * @param cacheNameParam 缓存名称
     * @return List&lt;CacheStatsInfo&gt;
     */
    public List<CacheStatsInfo> listCacheStats(String cacheNameParam) {
        logger.debug("获取缓存统计数据");

        // 无参则遍历所有redisTemplate 有参数则去找制定的redisTemplate
        List<CacheStatsInfo> statsList = new ArrayList<>();
        if (StringUtils.isEmpty(cacheNameParam)) {
            for (RedisTemplate redisTemplate : cacheManager.getRedisTemplates()) {
                Set<String> layeringCacheKeys = RedisHelper.scan(redisTemplate, CACHE_STATS_KEY_PREFIX + "*");
                statsList.addAll(getStats(redisTemplate, layeringCacheKeys, ""));
            }
        } else {
            Set<String> layeringCacheKeys = RedisHelper.scan(cacheManager.getRedisTemplate(cacheNameParam), CACHE_STATS_KEY_PREFIX + "*");
            statsList.addAll(getStats(cacheManager.getRedisTemplate(cacheNameParam), layeringCacheKeys, cacheNameParam));
        }

        if (CollectionUtils.isEmpty(statsList)) {
            return Collections.emptyList();
        }

        return statsList.stream().sorted(Comparator.comparing(CacheStatsInfo::getHitRate)).collect(Collectors.toList());
    }

    private List<CacheStatsInfo> getStats(RedisTemplate redisTemplate, Set<String> layeringCacheKeys, String cacheNameParam) {
        List<CacheStatsInfo> result = new ArrayList<>();
        for (String key : layeringCacheKeys) {
            if (!key.startsWith(CACHE_STATS_KEY_PREFIX + cacheNameParam)) {
                continue;
            }
            DataSerializer serializer = SerializerFactory.getSerializer(SerializeType.Json);

            CacheStatsInfo cacheStats = serializer.deserialize((String) redisTemplate.opsForValue().get(key), CacheStatsInfo.class);
            if (!Objects.isNull(cacheStats)) {
                result.add(cacheStats);
            }
        }

        return result;

    }

    /**
     * 同步缓存统计list
     */
    public void syncCacheStats() {
        // 清空统计数据
        resetCacheStat();
        DataSerializer serializer = SerializerFactory.getSerializer(SerializeType.Json);

        executor.scheduleWithFixedDelay(() -> {
            logger.debug("执行缓存统计数据采集定时任务");
            Set<AbstractCacheManager> cacheManagers = AbstractCacheManager.getCacheManager();
            for (AbstractCacheManager abstractCacheManager : cacheManagers) {
                // 获取CacheManager
                CacheManager cacheManager = abstractCacheManager;
                Collection<String> cacheNames = cacheManager.getCacheNames();
                for (String cacheName : cacheNames) {
                    // 获取Cache
                    Collection<Cache> caches = cacheManager.getCaches(cacheName);
                    RedisTemplate<String, Object> redisTemplate = this.cacheManager.getRedisTemplate(cacheName);
                    if (redisTemplate == null) {
                        continue;
                    }

                    for (Cache cache : caches) {
                        LayeringCache layeringCache = (LayeringCache) cache;
                        LayeringCacheSetting layeringCacheSetting = layeringCache.getLayeringCacheSetting();
                        // 加锁并增量缓存统计数据，缓存key=固定前缀 +缓存名称加 + 内部缓存名
                        String redisKey = CACHE_STATS_KEY_PREFIX + cacheName + layeringCacheSetting.getInternalKey();
                        Lock lock = new Lock(redisTemplate, redisKey, 60, 5000);
                        try {
                            if (lock.tryLock()) {

                                CacheStatsInfo cacheStats = serializer.deserialize((String) redisTemplate.opsForValue().get(redisKey), CacheStatsInfo.class);
                                if (Objects.isNull(cacheStats)) {
                                    cacheStats = new CacheStatsInfo();
                                }

                                // 设置缓存唯一标示
                                cacheStats.setCacheName(cacheName);
                                cacheStats.setInternalKey(layeringCacheSetting.getInternalKey());

                                cacheStats.setDepict(layeringCacheSetting.getDepict());

                                // 设置缓存统计数据
                                CacheStats layeringCacheStats = layeringCache.getCacheStats();

                                // 清空加载缓存时间

                                cacheStats.setRequestCount(cacheStats.getRequestCount() + layeringCacheStats.getAndResetCacheRequestCount());
                                cacheStats.setMissCount(cacheStats.getMissCount() + layeringCacheStats.getAndResetCachedMethodRequestCount());
                                cacheStats.setTotalLoadTime(cacheStats.getTotalLoadTime() + layeringCacheStats.getAndResetCachedMethodRequestTime());
                                cacheStats.setHitRate((cacheStats.getRequestCount() - cacheStats.getMissCount()) / (double) cacheStats.getRequestCount() * 100);

                                if (layeringCacheSetting.isUseFirstCache()) {
                                    CacheStats firstCacheStats = layeringCache.getFirstCache().getCacheStats();
                                    firstCacheStats.getAndResetCachedMethodRequestTime();
                                    cacheStats.setFirstCacheRequestCount(cacheStats.getFirstCacheRequestCount() + firstCacheStats.getAndResetCacheRequestCount());
                                    cacheStats.setFirstCacheMissCount(cacheStats.getFirstCacheMissCount() + firstCacheStats.getAndResetCachedMethodRequestCount());
                                }

                                if (layeringCacheSetting.isUseSecondCache()) {

                                    CacheStats secondCacheStats = layeringCache.getSecondCache().getCacheStats();
                                    secondCacheStats.getAndResetCachedMethodRequestTime();
                                    cacheStats.setSecondCacheRequestCount(cacheStats.getSecondCacheRequestCount() + secondCacheStats.getAndResetCacheRequestCount());
                                    cacheStats.setSecondCacheMissCount(cacheStats.getSecondCacheMissCount() + secondCacheStats.getAndResetCachedMethodRequestCount());
                                }

                                // 将缓存统计数据写到redis
                                redisTemplate.opsForValue().set(redisKey, serializer.serializeToString(cacheStats), 24, TimeUnit.HOURS);

//                                logger.info("Layering Cache 统计信息：{}", JSONSerializer.serialize(cacheStats));
                            }
                        } catch (Exception e) {
                            logger.error(e.getMessage(), e);
                        } finally {
                            lock.unlock();
                        }
                    }
                }
            }

            //  初始时间间隔是1分
        }, 1, 1, TimeUnit.MINUTES);
    }

    /**
     * 关闭线程池
     */
    public void shutdownExecutor() {
        executor.shutdown();
    }

    /**
     * 重置缓存统计数据
     */
    public void resetCacheStat() {
        Set<RedisTemplate<String, Object>> redisTemplates = cacheManager.getRedisTemplates();
        for (RedisTemplate<String, Object> redisTemplate : redisTemplates) {
            Set<String> layeringCacheKeys = RedisHelper.scan(redisTemplate, CACHE_STATS_KEY_PREFIX + "*");
            for (String key : layeringCacheKeys) {
                resetCacheStat(redisTemplate, key);
            }
        }
    }

    /**
     * 重置缓存统计数据
     *
     * @param cacheName cacheName
     * @param redisKey  redisKey
     */
    public void resetCacheStat(String cacheName, String redisKey) {
        RedisTemplate<String, Object> redisTemplate = cacheManager.getRedisTemplate(cacheName);
        DataSerializer serializer = SerializerFactory.getSerializer(SerializeType.Json);

        CacheStatsInfo cacheStats = serializer.deserialize((String) redisTemplate.opsForValue().get(redisKey), CacheStatsInfo.class);
        if (Objects.nonNull(cacheStats)) {
            cacheStats.clearStatsInfo();
            // 将缓存统计数据写到redis
            redisTemplate.opsForValue().set(redisKey, serializer.serializeToString(cacheStats), 24, TimeUnit.HOURS);
        }
    }

    /**
     * 重置缓存统计数据
     *
     * @param redisTemplate redisTemplate
     * @param redisKey      redisKey
     */
    public void resetCacheStat(RedisTemplate<String, Object> redisTemplate, String redisKey) {
        DataSerializer serializer = SerializerFactory.getSerializer(SerializeType.Json);

        CacheStatsInfo cacheStats = serializer.deserialize((String) redisTemplate.opsForValue().get(redisKey), CacheStatsInfo.class);
        if (Objects.nonNull(cacheStats)) {
            cacheStats.clearStatsInfo();
            // 将缓存统计数据写到redis
            redisTemplate.opsForValue().set(redisKey, serializer.serializeToString(cacheStats), 24, TimeUnit.HOURS);
        }
    }

    public void setCacheManager(AbstractCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }
}
